package cn.ham.file.handle;


import cn.ham.entity.Param;
import cn.ham.entity.RestApi;
import cn.ham.utils.DataType;
import com.alibaba.fastjson.JSONObject;
import org.objectweb.asm.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.asm.Opcodes;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author 18号
 * @date 2016/11/23 14:57
 */
public class ClassParse {

    private static Logger logger = LoggerFactory.getLogger(ClassParse.class);

    public static List<RestApi> parse(String classDir, String[] loaderClass ,int id) {
        List<String> classList = new ArrayList<String>();
        List<RestApi> list = new ArrayList<>();
        try {
            ScanFile.scan(classList, classDir, classDir);
            if (classList.size() == 0) {
                throw new RuntimeException("该目录没有Class");
            }

            URL[] urls = new URL[loaderClass.length];
            for (int i = 0; i < loaderClass.length; i++) {
                urls[i] = new URL(loaderClass[i]);
            }
            @SuppressWarnings("resource")
            URLClassLoader urlClassLoader = new URLClassLoader(urls);
            for (String fullName : classList) {
                try {
                    Class<?> clazz = urlClassLoader.loadClass(fullName);
                    // 解析类的注解
                    RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
                    String context = "";
                    if (requestMapping != null) {
                        context = requestMapping.value()[0];
                        context = !context.startsWith("/") ? "/" + context : context;
                    }
                    // 解析api方法的注解
                    Method[] methods = clazz.getMethods();
                    for (Method method : methods) {
                        RequestMapping methodRM = method.getAnnotation(RequestMapping.class);
                        if (methodRM == null) {
                            continue;
                        }
                        String operation = methodRM.value()[0];
                        operation = !operation.startsWith("/") ? "/" + operation : operation;
                        // 地址
                        String url = context + operation;
                        RequestMethod[] requestMethods = methodRM.method();
                        // 请求方式
                        RequestMethod requestMethod = null;
                        if (requestMethods.length > 0) {
                            requestMethod = methodRM.method()[0];
                        }

                        // 参数
                        String params = getMethodParam(clazz,method,urlClassLoader);
                        RestApi restApi = new RestApi();
                        restApi.setUrl(url);
                        restApi.setMethod(requestMethod == null ? null : requestMethod.toString());
                        restApi.setParams(params);
                        restApi.setProjectId(id);
                        list.add(restApi);
                        logger.info(restApi.getUrl()+" :: "+restApi.getMethod()+" :: "+restApi.getParams());
                    }
                } catch (ClassNotFoundException e) {
                    logger.error(fullName,e.getMessage());
                    continue;
                } catch (NoClassDefFoundError error) {
                    logger.error(fullName,error);
                    continue;
                }
            }
        } catch (Exception e) {
            logger.error("parse error:",e);
        }
        return list;
    }


    /**
     * 获取方法参数信息
     * @param clazz
     * @param method
     * @return
     */
    public static String getMethodParam(Class<?> clazz, final Method method,URLClassLoader urlClassLoader) {
        List<Param> params = new ArrayList<>();
        final Class<?>[] parameterTypes = method.getParameterTypes();
        if (parameterTypes == null || parameterTypes.length == 0) {
            return null;
        }
        final Type[] types = new Type[parameterTypes.length];
        final Type[] basicTypes = new Type[parameterTypes.length];
        for (int i = 0; i < parameterTypes.length; i++) {
            types[i] = Type.getType(parameterTypes[i]);

            String className = parameterTypes[i].getCanonicalName();
            if (!DataType.BASIC_DATA_TYPE.contains(className)) {// 自定义数据类型
                try {
                    Class<?> paramClazz = urlClassLoader.loadClass(className);
                    Field[] paramFields = paramClazz.getDeclaredFields();

                    for (Field field:paramFields){
                        Param p = new Param();
                        p.setType(field.getType().getSimpleName());
                        p.setName(field.getName());
                        params.add(p);
                    }
                } catch (ClassNotFoundException e) {
                    logger.error(className,e.getMessage());
                }
            }else {
                basicTypes[i] = Type.getType(parameterTypes[i]);
            }
        }
        final Param[] parameters = new Param[20];
        String className = clazz.getName();
        int lastDotIndex = className.lastIndexOf(".");
        className = className.substring(lastDotIndex + 1) + ".class";
        InputStream is = clazz.getResourceAsStream(className);
        try {
            ClassReader classReader = new ClassReader(is);
            classReader.accept(new ClassVisitor(Opcodes.ASM4) {
                @Override
                public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
                    Type[] argumentTypes = Type.getArgumentTypes(desc);
                    // 只解析含有参数的方法
                    if (!method.getName().equals(name) ||
                            !Arrays.equals(argumentTypes, basicTypes)) {
                        return null;
                    }

                    return new MethodVisitor(Opcodes.ASM4) {
                        @Override
                        public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
                            if (index > 0 && signature==null) {
                                Param param = new Param();
                                if (desc.lastIndexOf("/")>0 && desc.lastIndexOf(";")>0){
                                    desc = desc.substring(desc.lastIndexOf("/")+1,desc.lastIndexOf(";"));
                                }
                                param.setType(desc);
                                param.setName(name);
                                parameters[index-1] = param;
                            }
                        }
                    };
                }
            }, 0);
            for (Param p:parameters){
                if (!params.contains(p) && p!=null){
                    params.add(p);
                }
            }
        } catch (IOException e) {
            logger.error("getMethodParam error:",e.getMessage());
        }
        return JSONObject.toJSON(params).toString();
    }
}
